{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "dc6a3d0b",
   "metadata": {},
   "source": [
    "# 分类变量：\n",
    "##### 分类变量类似于枚举，拥有特定数量的值类型\n",
    "比如一项调查，询问你多久吃一次早餐，并提供四个选项:“从不”、“很少”、“大多数日子”或“每天”。在本例中，数据是分类的，因为答案属于一组固定的类别。如果对人们所拥有的汽车品牌进行调查，回答可以分为“本田”、“丰田”和“福特”。在本例中，数据也是分类的。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0c66246",
   "metadata": {},
   "source": [
    "### 预处理分类变量的方法：\n",
    "##### 1.删除分类变量\n",
    "处理分类变量最简单的方法是从数据集中删除它们。这种方法适用于该列中不包含有用信息的情况\n",
    "##### 2.标签编码\n",
    "标签编码将每个变量类型标记为不同的整数，对于类别有个唯一的排名，并不是所有的分类变量在值中都有一个明确的顺序，但是我们将那些有顺序的变量称为有序变量\n",
    "##### 3.One-Hot编码\n",
    "“One-hot”编码创建新列，表明原始数据中每个可能值的存在(或不存在)，与标签编码不同，one-hot编码不假定类别的顺序。因此，如果在分类数据中没有明确的顺序，我们把没有内在排序的分类变量称为名义变量\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "73ca2806",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\ProgramData\\Anaconda3\\lib\\site-packages\\pandas\\core\\frame.py:4308: SettingWithCopyWarning: \n",
      "A value is trying to be set on a copy of a slice from a DataFrame\n",
      "\n",
      "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
      "  return super().drop(\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Type</th>\n",
       "      <th>Method</th>\n",
       "      <th>Regionname</th>\n",
       "      <th>Rooms</th>\n",
       "      <th>Distance</th>\n",
       "      <th>Postcode</th>\n",
       "      <th>Bedroom2</th>\n",
       "      <th>Bathroom</th>\n",
       "      <th>Landsize</th>\n",
       "      <th>Lattitude</th>\n",
       "      <th>Longtitude</th>\n",
       "      <th>Propertycount</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>12167</th>\n",
       "      <td>u</td>\n",
       "      <td>S</td>\n",
       "      <td>Southern Metropolitan</td>\n",
       "      <td>1</td>\n",
       "      <td>5.0</td>\n",
       "      <td>3182.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>-37.85984</td>\n",
       "      <td>144.9867</td>\n",
       "      <td>13240.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6524</th>\n",
       "      <td>h</td>\n",
       "      <td>SA</td>\n",
       "      <td>Western Metropolitan</td>\n",
       "      <td>2</td>\n",
       "      <td>8.0</td>\n",
       "      <td>3016.0</td>\n",
       "      <td>2.0</td>\n",
       "      <td>2.0</td>\n",
       "      <td>193.0</td>\n",
       "      <td>-37.85800</td>\n",
       "      <td>144.9005</td>\n",
       "      <td>6380.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8413</th>\n",
       "      <td>h</td>\n",
       "      <td>S</td>\n",
       "      <td>Western Metropolitan</td>\n",
       "      <td>3</td>\n",
       "      <td>12.6</td>\n",
       "      <td>3020.0</td>\n",
       "      <td>3.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>555.0</td>\n",
       "      <td>-37.79880</td>\n",
       "      <td>144.8220</td>\n",
       "      <td>3755.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2919</th>\n",
       "      <td>u</td>\n",
       "      <td>SP</td>\n",
       "      <td>Northern Metropolitan</td>\n",
       "      <td>3</td>\n",
       "      <td>13.0</td>\n",
       "      <td>3046.0</td>\n",
       "      <td>3.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>265.0</td>\n",
       "      <td>-37.70830</td>\n",
       "      <td>144.9158</td>\n",
       "      <td>8870.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6043</th>\n",
       "      <td>h</td>\n",
       "      <td>S</td>\n",
       "      <td>Western Metropolitan</td>\n",
       "      <td>3</td>\n",
       "      <td>13.3</td>\n",
       "      <td>3020.0</td>\n",
       "      <td>3.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>673.0</td>\n",
       "      <td>-37.76230</td>\n",
       "      <td>144.8272</td>\n",
       "      <td>4217.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      Type Method             Regionname  ...  Lattitude  Longtitude  Propertycount\n",
       "12167    u      S  Southern Metropolitan  ...  -37.85984    144.9867        13240.0\n",
       "6524     h     SA   Western Metropolitan  ...  -37.85800    144.9005         6380.0\n",
       "8413     h      S   Western Metropolitan  ...  -37.79880    144.8220         3755.0\n",
       "2919     u     SP  Northern Metropolitan  ...  -37.70830    144.9158         8870.0\n",
       "6043     h      S   Western Metropolitan  ...  -37.76230    144.8272         4217.0\n",
       "\n",
       "[5 rows x 12 columns]"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd \n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# 自己定义的方法，用于比较处理缺失值的不同方法,内部用的是随机森林模型\n",
    "from score_dataset import score_dataset\n",
    "\n",
    "# Read the data \n",
    "data = pd.read_csv('../melb_data.csv')\n",
    "\n",
    "# Separate target from predictors 将目标与预测分开\n",
    "y = data.Price\n",
    "X = data.drop(['Price'], axis=1)\n",
    "\n",
    "# Divide data into training and validation subsets\n",
    "X_train_full, X_valid_full, y_train, y_valid = train_test_split(X, y, train_size=0.8, test_size=0.2,random_state=0)\n",
    "\n",
    "# Drop columns with missing values (simplest approach)\n",
    "cols_with_missing = [col for col in X_train_full.columns if X_train_full[col].isnull().any()]\n",
    "X_train_full.drop(cols_with_missing, axis=1, inplace=True)\n",
    "X_valid_full.drop(cols_with_missing, axis=1, inplace=True)\n",
    "\n",
    "# \"Cardinality\" means the number of unique values in a column\n",
    "# Select categorical columns with relatively low cardinality (convenient but arbitrary)\n",
    "# “基数”是指在一列中唯一值的数量,选择基数相对较低的分类列（方便但任意）\n",
    "low_cardinality_cols = [cname for cname in X_train_full.columns if X_train_full[cname].nunique() < 10 and \n",
    "                        X_train_full[cname].dtype == \"object\"]\n",
    "\n",
    "# Select numerical columns\n",
    "numerical_cols = [cname for cname in X_train_full.columns if X_train_full[cname].dtype in ['int64', 'float64']]\n",
    "\n",
    "# Keep selected columns only\n",
    "my_cols = low_cardinality_cols + numerical_cols\n",
    "X_train = X_train_full.loc[:,my_cols].copy()\n",
    "X_valid = X_valid_full.loc[:,my_cols].copy()\n",
    "# 用head()方法瞄一眼X_train\n",
    "X_train.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "aad12f21",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Categorical variables:\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "['Type', 'Method', 'Regionname']"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 获取分类列表\n",
    "s = (X_train.dtypes == 'object')\n",
    "object_cols = list(s[s].index)\n",
    "\n",
    "print(\"Categorical variables:\")\n",
    "object_cols"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "22c587f4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MAE from Approach 1 (Drop categorical variables):\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "174632.25689207987"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'''\n",
    "1.删除分类变量\n",
    "   处理分类变量最简单的方法是从数据集中删除它们。这种方法适用于该列中不包含有用信息的情况\n",
    "'''\n",
    "drop_X_train = X_train.select_dtypes(exclude=['object'])\n",
    "drop_X_valid = X_valid.select_dtypes(exclude=['object'])\n",
    "\n",
    "print(\"MAE from Approach 1 (Drop categorical variables):\")\n",
    "score_dataset(drop_X_train, drop_X_valid, y_train, y_valid)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "0bf1b18f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MAE from Approach 2 (Label Encoding):\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "165256.28786135072"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'''\n",
    "2.标签编码\n",
    "   标签编码将每个变量类型标记为不同的整数\n",
    "'''\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "\n",
    "# 复制一份数据防止改变源数据\n",
    "label_X_train = X_train.copy()\n",
    "label_X_valid = X_valid.copy()\n",
    "\n",
    "# 将标签编码器分别应用于每一列\n",
    "label_encoder = LabelEncoder()\n",
    "for col in object_cols:\n",
    "    label_X_train[col] = label_encoder.fit_transform(X_train[col])\n",
    "    label_X_valid[col] = label_encoder.transform(X_valid[col])\n",
    "\n",
    "print(\"MAE from Approach 2 (Label Encoding):\")\n",
    "score_dataset(label_X_train, label_X_valid, y_train, y_valid)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "581858fa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MAE from Approach 3 (One-Hot Encoding):\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "166500.44717161093"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'''\n",
    "方法3.One-Hot编码\n",
    "我们使用scikit-learn的OneHotEncoder类来获得one-hot编码。有许多参数可定义。\n",
    "设置handle_unknown='ignore'，以避免在验证数据包含训练数据中没有包括的值时发生错误\n",
    "设置sparse=False可以确保将已编码的列作为numpy数组(而不是稀疏矩阵)返回。\n",
    "'''\n",
    "from sklearn.preprocessing import OneHotEncoder\n",
    "\n",
    "# 将one-hot编码器分别应用于每一列分类变量\n",
    "OH_encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)\n",
    "OH_cols_train = pd.DataFrame(OH_encoder.fit_transform(X_train[object_cols]))\n",
    "OH_cols_valid = pd.DataFrame(OH_encoder.transform(X_valid[object_cols]))\n",
    "\n",
    "# One-hot编码时移除了index;补回来\n",
    "OH_cols_train.index = X_train.index\n",
    "OH_cols_valid.index = X_valid.index\n",
    "\n",
    "# 删除分类列(将替换为One-hot编码),留下编码列\n",
    "num_X_train = X_train.drop(object_cols, axis=1)\n",
    "num_X_valid = X_valid.drop(object_cols, axis=1)\n",
    "\n",
    "# 向数值特征添加One-hot编码列\n",
    "OH_X_train = pd.concat([num_X_train, OH_cols_train], axis=1)\n",
    "OH_X_valid = pd.concat([num_X_valid, OH_cols_valid], axis=1)\n",
    "\n",
    "print(\"MAE from Approach 3 (One-Hot Encoding):\")\n",
    "score_dataset(OH_X_train, OH_X_valid, y_train, y_valid)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0540b4bd",
   "metadata": {},
   "source": [
    "### 总结：\n",
    "删除分类变量(方法1)的性能最差，因为它的MAE分数最高。至于另外两种方法，由于返回的MAE分数值非常接近，没有太大差异。\n",
    "通常，one-hot编码(方法3)的效果最好，删除分类变量(方法1)的效果最差，但还得视情况而定。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
